In [10]:
%%html
<link rel="stylesheet" type="text/css" href="theme/sixty_north.css">


The Jupyter Elm Kernel

Interactive notebooks for Elm


Austin Bingham
twitter: @austin_bingham
email: austin@sixty-north.com


What is a Jupyter notebook?

The Jupyter Notebook is an open-source web application that allows you to create and share documents that contain live code, equations, visualizations and explanatory text.

-- jupyter.org

What is a Jupyter kernel?

A ‘kernel’ is a program that runs and introspects the user’s code.

-- ipython.org

  • Receives code from the client when a cell is executed
  • Runs the code (for some definition of "run")
  • Returns output and status of execution

The Elm kernel

Support for Elm code cells in Jupyter notebooks


  • Accumulates code cells
  • Compiles accumulated code on "-- compile-code"
  • Compilation goes into a temporary file
  • Compilaton results are shipped back to the web client
  • elm-stuff if kept between compilations

Kernel initialization


class ElmKernel(Kernel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._code = []
        self._tempdir = TemporaryDirectory()

Compilation and execution

Receive code from the web client


def do_execute(self, code, . . .):
    self._code.append(code)
    if self._should_compile:
        try:
            code = "\n".join(self._code)
            self._code = []
            self._compile(code)
        except Exception as exc:
            self._send_error_result(str(exc))
            return {'status': 'error' . . . }
    return {'status': 'ok' . . . }

Compilation and execution

Write code to file and compile it, reporting any failures


def _compile(self, code):
    with self._tempfile('input.elm') as infile,\
         self._tempfile('index.js') as outfile:
        with open(infile, mode='wt') as f:
            f.write(code)
        try:
            # compile in a subprocess (next slide)
        except subprocess.CalledProcessError as err:
            self._send_error_result(err.stdout)
        except Exception as err:
            self._send_error_result(repr(err))
            raise

Compilation and execution

Run compiler, read output, and report success


subprocess.run(
    ['elm-make', infile, '--yes', '--output={}'.format(outfile)],
    cwd=self._tempdir.name,
    check=True,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    encoding=sys.getdefaultencoding())

with open(outfile, mode='rt') as f:
    javascript = f.read()

self._send_success_result(javascript)

Reporting success

Producing JavaScript embedding code


module_name = "Main"
div_id = 'elm-div-' + str(self.execution_count)
template = """
    var defineElm = function(cb) {{
        if (this.Elm) {{
            this.oldElm = this.Elm;
        }}
        var define = null;
        {js}
        cb();
    }};
    var obj = new Object();
    defineElm.bind(obj)(function(){{
        var mountNode = document.getElementById('{div_id}');
        obj.Elm. {module_name}.embed(mountNode);
    }});"""

Reporting success

Injecting HTML into the client


self.send_response(
    self.iopub_socket,
    'display_data',
    {
        'metadata': {},
        'data': {
            'text/html': '<div id="' + div_id + '"></div>'
        }
    }
)

Reporting success

Sending JavaScript to client


javascript = template.format(
    js=javascript,
    module_name=module_name,
    div_id=div_id)

self.send_response(
    self.iopub_socket,
    'display_data',
    {
        'metadata': {},
        'data': {
            'application/javascript': javascript
        }
    })

Good news! It seems to work!

Room for improvement

  • Improved way of signalling for compilation
  • Some way to re-use code cells
  • Better support for elm-package.json
  • Automated tests / travis-ci
  • An "elm-repl" experience

Get involved

You can find everything at the github project page: https://github.com/abingham/jupyter-elm-kernel

You can help with:

  • Feedback about actual use
  • Feature ideas and bug reports
  • Pull requests

Thanks!